#include <signal.h>
#include <ServerCore.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <vector>
#include <string>

using namespace std;

static void readDefaultVariable()
{

}

static void addString2ObjectType(UA_Server* server, UA_NodeId& ObjectTypeId, char* name, char* value, bool if_madatory  = false)
{
    UA_VariableAttributes StrAttr = UA_VariableAttributes_default;
    StrAttr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", name);
    UA_String AttrValue = UA_STRING(value);
    UA_Variant_setScalar(&StrAttr.value, &AttrValue, &UA_TYPES[UA_TYPES_STRING]);
    UA_NodeId StrNodeId;
    UA_Server_addVariableNode(server, UA_NODEID_NULL, ObjectTypeId, UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME(1, name), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), StrAttr, NULL, &StrNodeId);
    if (if_madatory)
        UA_Server_addReference(server, UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE), StrNodeId, 
            UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
}

static void addUInt322ObjectType(UA_Server* server, UA_NodeId& ObjectTypeId, char* name, int value, bool if_madatory = false)
{
    UA_VariableAttributes IntAttr = UA_VariableAttributes_default;
    IntAttr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", name);
    UA_UInt32 AttrValue = value;
    UA_Variant_setScalar(&IntAttr.value, &AttrValue, &UA_TYPES[UA_TYPES_UINT32]);
    UA_NodeId IntNodeId;
    UA_Server_addVariableNode(server, UA_NODEID_NULL, ObjectTypeId, UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME(1, name), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), IntAttr, NULL, &IntNodeId);
    if (if_madatory)
        UA_Server_addReference(server, UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE), IntNodeId,
            UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
}

static void addFloat2ObjectType(UA_Server* server, UA_NodeId& ObjectTypeId, char* name, float value, bool if_madatory = false)
{
    UA_VariableAttributes FloatAttr = UA_VariableAttributes_default;
    FloatAttr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", name);
    UA_Float AttrValue = value;
    UA_Variant_setScalar(&FloatAttr.value, &AttrValue, &UA_TYPES[UA_TYPES_FLOAT]);
    UA_NodeId FloatNodeId;
    UA_Server_addVariableNode(server, UA_NODEID_NULL, ObjectTypeId, UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME(1, name), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), FloatAttr, NULL, &FloatNodeId);
    if (if_madatory)
        UA_Server_addReference(server, UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE), FloatNodeId,
            UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
}


static void addBool2ObjectType(UA_Server* server, UA_NodeId& ObjectTypeId, char* name, bool value, bool if_madatory = false)
{
    UA_VariableAttributes BoolAttr = UA_VariableAttributes_default;
    BoolAttr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", name);
    UA_Boolean AttrValue = value;
    UA_Variant_setScalar(&BoolAttr.value, &AttrValue, &UA_TYPES[UA_TYPES_BOOLEAN]);
    UA_NodeId BoolNodeId;
    UA_Server_addVariableNode(server, UA_NODEID_NULL, ObjectTypeId, UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
        UA_QUALIFIEDNAME(1, name), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), BoolAttr, NULL, &BoolNodeId);
    if (if_madatory)
        UA_Server_addReference(server, UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE), BoolNodeId,
            UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
}

inline static void addInfomationModel(UA_Server* server, UA_NodeId object_type, UA_NodeId node_id, char* name)
{
    UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
    oAttr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", name);
    UA_Server_addObjectNode(server, UA_NODEID_NULL, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
        UA_QUALIFIEDNAME(1, name), object_type, oAttr, NULL, &node_id);
}


static int findChildId(UA_Server* server,
    UA_NodeId parentNode,
    UA_NodeId referenceType,
    const UA_QualifiedName targetName,
    UA_NodeId* result)
{
    int ret = 0;
    UA_RelativePathElement rpe;
    UA_RelativePathElement_init(&rpe);
    rpe.referenceTypeId = referenceType;
    rpe.isInverse = false;
    rpe.includeSubtypes = false;
    rpe.targetName = targetName;

    UA_BrowsePath bp;
    UA_BrowsePath_init(&bp);
    bp.startingNode = parentNode;
    bp.relativePath.elementsSize = 1;
    bp.relativePath.elements = &rpe;

    UA_BrowsePathResult bpr = UA_Server_translateBrowsePathToNodeIds(server, &bp);
    if (bpr.statusCode != UA_STATUSCODE_GOOD || bpr.targetsSize < 1)
    {
        printf("error: %s, targetsSize: %d\n",
            UA_StatusCode_name(bpr.statusCode), bpr.targetsSize);
        ret = -1;
    }
    else
    {
        UA_NodeId_copy(&bpr.targets[0].targetId.nodeId, result);
    }


    UA_BrowsePathResult_deleteMembers(&bpr);

    return ret;
}


ServerCore::ServerCore()
{
    //pThis = 0;
    init_variable();
    server = UA_Server_new();
    constructObjectType(server);
    addVariableNode();
    UA_ServerConfig_setDefault(UA_Server_getConfig(server));
}

ServerCore::~ServerCore()
{
    this->running = false;
    Sleep(10);
    UA_Server_delete(this->server);

}

void ServerCore::constructObjectType(UA_Server* server)
{
    UA_ObjectTypeAttributes prlineAttr = UA_ObjectTypeAttributes_default;
    prlineAttr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", (char*)"PRLineType");
    UA_Server_addObjectTypeNode(server, PRLineTypeId, UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE), UA_QUALIFIEDNAME(1, (char*)"PRLineType"), prlineAttr, NULL, NULL);
    addUInt322ObjectType(server, PRLineTypeId, (char*)"Num", prline.num);
    addString2ObjectType(server, PRLineTypeId, (char*)"Describe", prline.describe);

    UA_ObjectTypeAttributes stationAttr = UA_ObjectTypeAttributes_default;
    stationAttr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", (char*)"StationType");
    UA_Server_addObjectTypeNode(server, StationTypeId, UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE), UA_QUALIFIEDNAME(1, (char*)"StationType"), stationAttr, NULL, NULL);
    vector<string>name_list = {"FactoryNum", "DeviceNum", "ArmNum", "AssembleNum", "PLCNum", "VisionModule", "FaultType"};
    addString2ObjectType(server, StationTypeId, (char*)"FactoryNum", station_info.factory_info);
    addString2ObjectType(server, StationTypeId, (char*)"DeviceNum", station_info.device_info);
    addString2ObjectType(server, StationTypeId, (char*)"ArmNum", station_info.arm_info);
    addString2ObjectType(server, StationTypeId, (char*)"AssembleNum", station_info.assemble_info);
    addString2ObjectType(server, StationTypeId, (char*)"PLCNum", station_info.plc_info);
    addString2ObjectType(server, StationTypeId, (char*)"VisionModule", station_info.vision_module);
    addString2ObjectType(server, StationTypeId, (char*)"FaultType", station_info.fault_type);
    addFloat2ObjectType(server, StationTypeId, (char*)"WorkingVoltage", station_info.valtage);
    addFloat2ObjectType(server, StationTypeId, (char*)"AirPressure", station_info.air_pressure);
    UA_ObjectTypeAttributes cpuAttr = UA_ObjectTypeAttributes_default;
    cpuAttr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", (char*)"CPUType");
    UA_Server_addObjectTypeNode(server, CPUTypeId, StationTypeId,UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE), 
        UA_QUALIFIEDNAME(1, (char*)"CPUType"), cpuAttr, NULL, NULL);
    addString2ObjectType(server, CPUTypeId, (char*)"CPUInfo", station_info.pc_info.CPUInfo);
    addString2ObjectType(server, CPUTypeId, (char*)"MemoryInfo", station_info.pc_info.MemInfo);
    addString2ObjectType(server, CPUTypeId, (char*)"DiskInfo", station_info.pc_info.DiskInfo);

    UA_ObjectTypeAttributes robotAttr = UA_ObjectTypeAttributes_default;
    robotAttr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", (char*)"RobotType");
    UA_Server_addObjectTypeNode(server, RobotTypeId, UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE), UA_QUALIFIEDNAME(1, (char*)"RobotType"), robotAttr, NULL, NULL);
    addBool2ObjectType(server, RobotTypeId, (char*)"FetchLabelSignal1", robot_module.fetch_signal1);
    addBool2ObjectType(server, RobotTypeId, (char*)"FetchLabelSignal2", robot_module.fetch_signal2);
    addBool2ObjectType(server, RobotTypeId, (char*)"FetchLabelSignal3", robot_module.fetch_signal3);
    addBool2ObjectType(server, RobotTypeId, (char*)"CodeAnalysisSignal", robot_module.analysis_signal);
    addBool2ObjectType(server, RobotTypeId, (char*)"LabelSignal1", robot_module.label_signal1);
    addBool2ObjectType(server, RobotTypeId, (char*)"LabelSignal2", robot_module.label_signal2);
    addBool2ObjectType(server, RobotTypeId, (char*)"LabelSignal3", robot_module.label_signal3);

    UA_ObjectTypeAttributes visionAttr = UA_ObjectTypeAttributes_default;
    visionAttr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", (char*)"VisionType");
    UA_Server_addObjectTypeNode(server, VisionTypeId, UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE), UA_QUALIFIEDNAME(1, (char*)"VisionType"), visionAttr, NULL, NULL);
    addBool2ObjectType(server, VisionTypeId, (char*)"CaptureSignal", vision_module.capture_signal);
    addBool2ObjectType(server, VisionTypeId, (char*)"PositionSignal", vision_module.pos_signal);

    UA_ObjectTypeAttributes stripAttr = UA_ObjectTypeAttributes_default;
    stripAttr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", (char*)"StripType");
    UA_Server_addObjectTypeNode(server, StripTypeId, UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE), UA_QUALIFIEDNAME(1, (char*)"StripType"), stripAttr, NULL, NULL);
    addFloat2ObjectType(server, StripTypeId, (char*)"OutputSpeed", strip_module.speed);
    addFloat2ObjectType(server, StripTypeId, (char*)"ArriveDelay", strip_module.arrive_delay);
    addFloat2ObjectType(server, StripTypeId, (char*)"AlarmDelay", strip_module.alarm_delay);

    UA_ObjectTypeAttributes manageAttr = UA_ObjectTypeAttributes_default;
    manageAttr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", (char*)"ManageType");
    UA_Server_addObjectTypeNode(server, ManageTypeId, UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
        UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE), UA_QUALIFIEDNAME(1, (char*)"ManageType"), manageAttr, NULL, NULL);

}


void ServerCore::init_variable()
{
    memset(&prline, 0, sizeof(PRLine));
    memset(&station_info, 0, sizeof(StationInfo));
    memset(&robot_module, 0, sizeof(RobotModule));
    memset(&vision_module, 0, sizeof(VisionModule));
    memset(&strip_module, 0, sizeof(StripModule));
    memset(&manage_module, 0, sizeof(ManageModule));
}

void ServerCore::RobotBeforeRead(UA_Server* server,
    const UA_NodeId* sessionId, void* sessionContext,
    const UA_NodeId* nodeid, void* nodeContext,
    const UA_NumericRange* range, const UA_DataValue* data)
{
    return;
}

void ServerCore::RobotAfterWrite(UA_Server* server,
    const UA_NodeId* sessionId, void* sessionContext,
    const UA_NodeId* nodeid, void* nodeContext,
    const UA_NumericRange* range, const UA_DataValue* data)
{
    return;
}

void ServerCore::addRobotNode()
{
    ModeulType type = ROBOT;
    UA_NodeId node_id = *node_dict[type];
    UA_NodeId parent_node_id = UA_NODEID_NUMERIC(1, type);
    addInfomationModel(server, parent_node_id, node_id, (char*)name_dict[type].c_str());
    UA_ValueCallback callback;
    callback.onRead = RobotBeforeRead;
    callback.onWrite = RobotAfterWrite;
    //UA_Server_setVariableNode_valueCallback(server, node_id, callback);

    auto writeleaf = [=](char* name, UA_Boolean value) {
        value = true;
        UA_NodeId itemId;
        UA_Variant ua_value;
        if (findChildId(server, parent_node_id, UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), UA_QUALIFIEDNAME(1, name), &itemId) == 0)
        {
            UA_Variant_init(&ua_value);
            UA_Variant_setScalar(&ua_value, &value, &UA_TYPES[UA_TYPES_BOOLEAN]);
            UA_Server_writeValue(server, itemId, ua_value);
        }
    };
    writeleaf((char*)"FetchLabelSignal1", robot_module.fetch_signal1);
    writeleaf((char*)"FetchLabelSignal2", robot_module.fetch_signal2);
    writeleaf((char*)"FetchLabelSignal3", robot_module.fetch_signal3);
    writeleaf((char*)"CodeAnalysisSignal", robot_module.analysis_signal);
    writeleaf((char*)"LabelSignal1", robot_module.label_signal1);
    writeleaf((char*)"LabelSignal2", robot_module.label_signal2);
    writeleaf((char*)"LabelSignal3", robot_module.label_signal3);
}

void ServerCore::VisionBeforeRead(UA_Server* server,
    const UA_NodeId* sessionId, void* sessionContext,
    const UA_NodeId* nodeid, void* nodeContext,
    const UA_NumericRange* range, const UA_DataValue* data)
{
    return;
}

void ServerCore::VisionAfterWrite(UA_Server* server,
    const UA_NodeId* sessionId, void* sessionContext,
    const UA_NodeId* nodeid, void* nodeContext,
    const UA_NumericRange* range, const UA_DataValue* data)
{
    return;
}

void ServerCore::addVisionNode()
{
    ModeulType type = VISION;
    UA_NodeId node_id = *node_dict[type];
    UA_NodeId parent_node_id = UA_NODEID_NUMERIC(1, type);
    addInfomationModel(server, parent_node_id, node_id, (char*)name_dict[type].c_str());
    UA_ValueCallback callback;
    callback.onRead = VisionBeforeRead;
    callback.onWrite = VisionAfterWrite;
    UA_Server_setVariableNode_valueCallback(server, node_id, callback);
    auto writeleaf = [=](char* name, UA_Boolean value) {
        UA_NodeId itemId;
        UA_Variant ua_value;
        if (findChildId(server, parent_node_id, UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, name), &itemId) == 0)
        {
            UA_Variant_init(&ua_value);
            UA_Variant_setScalar(&ua_value, &value, &UA_TYPES[UA_TYPES_BOOLEAN]);
            UA_Server_writeValue(server, itemId, ua_value);
        }
    };
    writeleaf((char*)"CaptureSignal", vision_module.capture_signal);
    writeleaf((char*)"PositionSignal", vision_module.pos_signal);
}

void ServerCore::addVariableNode()
{    
    addRobotNode();
    addVisionNode();
    
}

//void ServerCore::stopHandler(int sig) {
//    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "received ctrl-c");
//    this->running = false;
//}


void ServerCore::server_stop()
{
    this->running = false;
}

bool ServerCore::if_running()
{
    return this->running;
}



int ServerCore::run() {
    /*signal(SIGINT, stopHandler);
    signal(SIGTERM, stopHandler);*/
    this->running = true;
    UA_StatusCode retval = UA_Server_run(this->server, &this->running);
    
    return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
    //UA_ServerConfig_delete(config);
    //return (int)retval;
}



int ServerCore::renameNodeId(ModeulType node, std::string new_name)
{
    
    //UA_Server_deleteNode(server, )
    return 0;
}